﻿Imports System
Imports System.Xml
Imports System.Xml.Schema
Imports System.Xml.XPath

Public Class frmMain
   Private m_strCTXOutputFileName As String
   Private m_strInputFolderName As String
   Private m_intCountofTracksProcessed As Integer
   Private m_strLogFileName As String

   Private Sub frmMain_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load


      m_strCTXOutputFileName = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) & "\GarminWorkout.tcx"
      m_strInputFolderName = Environment.GetFolderPath(Environment.SpecialFolder.Desktop)
      m_intCountofTracksProcessed = 0
      m_strLogFileName = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) & "\GarminTCXBuilderLog.txt"

      txtFolderInput.Text = m_strInputFolderName
      txtFileOutput.Text = m_strCTXOutputFileName

      txtAge.Text = My.Settings.UserAge
      txtWeightLbs.Text = My.Settings.UserWeight
      txtSex.Text = My.Settings.UserSex
      txtAvgBPMHR.Text = My.Settings.UserAvgBPMHR
      txtMaxBPMHR.Text = My.Settings.UserMaxBPMHR
      chkUseAvgHR.Checked = My.Settings.UseHRInputForCalories
      txtUnitModel.Text = My.Settings.UnitModel
      txtUnitID.Text = My.Settings.UnitID
      txtUnitLapTrigger.Text = My.Settings.LapTrigger
   End Sub

   Private Sub btnFolderInput_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnFolderInput.Click
      Dim fldr As New FolderBrowserDialog
      With fldr
         .RootFolder = Environment.SpecialFolder.Desktop
         .Description = "Select the source directory"
         If .ShowDialog = DialogResult.OK Then
            txtFolderInput.Text = .SelectedPath & "\"
            m_strInputFolderName = .SelectedPath & "\"
         End If
      End With
   End Sub

   Private Sub btnBuildTCXFile_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnBuildTCXFile.Click
      m_intCountofTracksProcessed = 0
      'write header
      WriteToFile("<?xml version=""1.0"" encoding=""UTF-8"" standalone=""no"" ?>")
      WriteToFile("<TrainingCenterDatabase xmlns=""http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2"" xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xsi:schemaLocation=""http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2 http://www.garmin.com/xmlschemas/TrainingCenterDatabasev2.xsd"">")
      WriteToFile("<Activities>")

      WriteToFile("<Activity Sport=""" & IIf(cmbActivitySport.Text = "Activity Sport", "Biking", cmbActivitySport.Text) & """>")

      ''First make sure there is only one root 
      For Each strLapFileName As String In System.IO.Directory.EnumerateFiles(m_strInputFolderName, "*.dat", IO.SearchOption.TopDirectoryOnly)
         ' need to pad file names to load them in proper order
         Dim f As New System.IO.FileInfo(strLapFileName)

         If f.Name = "data.dat" Then
            System.IO.File.Delete(strLapFileName)
         Else
            Dim strNewName As String = ""
            strNewName = f.Name.Replace("Lap", "")
            strNewName = strNewName.PadLeft(9, "0")
            strNewName = "Lap" & strNewName
            System.IO.File.Move(f.FullName, f.DirectoryName & "\" & strNewName)
         End If

      Next

      'write the laps
      For Each strLapFileName As String In System.IO.Directory.EnumerateFiles(m_strInputFolderName, "*.dat", IO.SearchOption.TopDirectoryOnly)
         FixTrackXMLTag(strLapFileName)
         WriteLap(strLapFileName)
      Next

      'write footer

      WriteToFile("<Creator xsi:type=""Device_t"">")
      WriteToFile(" <Name>" & txtUnitModel.Text & "</Name>")
      WriteToFile(" <UnitId>" & txtUnitID.Text & "</UnitId>")
      WriteToFile(" <ProductID>625</ProductID>")
      WriteToFile("<Version>")
      WriteToFile(" <VersionMajor>3</VersionMajor>")
      WriteToFile(" <VersionMinor>20</VersionMinor>")
      WriteToFile(" <BuildMajor>0</BuildMajor>")
      WriteToFile(" <BuildMinor>0</BuildMinor>")
      WriteToFile(" </Version>")
      WriteToFile(" </Creator>")
      WriteToFile(" </Activity>")
      WriteToFile(" </Activities>")
      WriteToFile("<Author xsi:type=""Application_t"">")
      WriteToFile(" <Name>" & txtUnitModel.Text & "</Name>")
      WriteToFile("<Build>")
      WriteToFile("<Version>")
      WriteToFile(" <VersionMajor>3</VersionMajor>")
      WriteToFile(" <VersionMinor>20</VersionMinor>")
      WriteToFile(" <BuildMajor>0</BuildMajor>")
      WriteToFile(" <BuildMinor>0</BuildMinor>")
      WriteToFile(" </Version>")
      WriteToFile(" <Type>Release</Type>")
      WriteToFile(" </Build>")
      WriteToFile(" <LangID>EN</LangID>")
      WriteToFile(" <PartNumber>006-B0625-00</PartNumber>")
      WriteToFile(" </Author>")
      WriteToFile(" </TrainingCenterDatabase>")

      MessageBox.Show("Finished writing TCX file")

      Dim myDocument As New XmlDocument
      myDocument.Load(m_strCTXOutputFileName)
      myDocument.Schemas.Add("http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2", "http://www.garmin.com/xmlschemas/TrainingCenterDatabasev2.xsd")
      Dim eventHandler As ValidationEventHandler = New ValidationEventHandler(AddressOf ValidationEventHandler)
      myDocument.Validate(eventHandler)

      MessageBox.Show("Finished verifying TCX file")
   End Sub

   Private Sub btnFileOutput_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnFileOutput.Click
      Dim SaveFileDialog1 As New SaveFileDialog
      With SaveFileDialog1
         .InitialDirectory = Environment.SpecialFolder.Desktop
         .Filter = "TCX files (*.tcx)|*.tcx|Text files (*.txt)|*.txt|All files (*.*)|*.*"
         .Title = "Select the destination file"
         If .ShowDialog = DialogResult.OK Then
            m_strCTXOutputFileName = .FileName
            txtFileOutput.Text = .FileName.ToString()
         End If
      End With
   End Sub

   Private Sub WriteToFile(ByVal strText As String)
      Dim swriter As System.IO.StreamWriter
      swriter = System.IO.File.AppendText(m_strCTXOutputFileName)
      swriter.WriteLine(strText)
      swriter.Close()
   End Sub

   Private Sub WriteToLog(ByVal strText As String)
      Dim swriter As System.IO.StreamWriter
      swriter = System.IO.File.AppendText(m_strLogFileName)
      swriter.WriteLine(strText)
      swriter.Close()
   End Sub

   Private Sub WriteLap(ByVal strLapFileName As String)

      ' one lap = one track with multiple trackpoints

      m_intCountofTracksProcessed += 1

      Dim xmlDoc As New System.Xml.XmlDocument
      xmlDoc.Load(strLapFileName)

      Dim trackData As New Track(xmlDoc)

      If m_intCountofTracksProcessed = 1 Then
         'The Id of the file is the time of the first lap
         WriteToFile("<Id>" & trackData.Trackpoints(0).Time & "</Id>")
      End If

      'Now write the lap to tcx file
      WriteToFile("<Lap StartTime=""" & trackData.Trackpoints(0).Time & """>")


      Dim tStart As DateTime = trackData.Trackpoints(0).Time
      Dim tEnd As DateTime = trackData.Trackpoints(trackData.Trackpoints.Count - 1).Time

      Dim lngTotalTimeSeconds As Long = DateAndTime.DateDiff(DateInterval.Second, tStart, tEnd)

      WriteToFile("<TotalTimeSeconds>" & lngTotalTimeSeconds.ToString & "</TotalTimeSeconds>")

      WriteToFile("<DistanceMeters>" & CDec(trackData.Trackpoints(trackData.Trackpoints.Count - 1).DistanceMeters) - CDec(trackData.Trackpoints(0).DistanceMeters) & "</DistanceMeters>")

      'Calculate speed in meters / second

      Dim decMaxSpeedMtrSec As Decimal = 0
      Dim decLapSpeed As Decimal = 0
      For i = 1 To trackData.Trackpoints.Count - 2
         decLapSpeed = (CDec(trackData.Trackpoints(i + 1).DistanceMeters) - CDec(trackData.Trackpoints(i).DistanceMeters)) / DateAndTime.DateDiff(DateInterval.Second, CDate(trackData.Trackpoints(i).Time), CDate(trackData.Trackpoints(i + 1).Time))
         If decLapSpeed > decMaxSpeedMtrSec Then
            decMaxSpeedMtrSec = decLapSpeed
         End If
         'Debug.WriteLine("Current Lap Speed= " & decLapSpeed & " Max Speed= " & decMaxSpeedMtrSec)
      Next

      WriteToFile("<MaximumSpeed>" & decMaxSpeedMtrSec & "</MaximumSpeed>")


      ' Calorie calculation based on heart rate
      '   Based on the following formulas:
      'Using VO2max
      '   Men: C/min = (-59.3954 + (-36.3781 + 0.271 x age + 0.394 x weight + 0.404 x VO2max + 0.634 x HR))/4.184
      '   Women: C/min = (-59.3954 + (0.274 x age + 0.103 x weight + 0.380 x VO2max + 0.450 x HR)) / 4.184

      'Without VO2max
      '   Men: C/min = (-55.0969 + 0.6309 x HR + 0.1988 x weight + 0.2017 x age) / 4.184
      '   Women: C/min = (-20.4022 + 0.4472 x HR + 0.1263 x weight + 0.074 x age) / 4.184
      'weight is in kg

      Dim k As Int16 = 0
      Dim lngCalories As Long = 0
      Dim lngAveHeartRateBpm As Long = 0
      Dim lngMaxHeartRateBpm As Long = 0
      For Each tr As Trackpoint In trackData.Trackpoints

         If Not IsNothing(tr.HeartRateBpm) Then
            k += 1
            lngAveHeartRateBpm += CLng(tr.HeartRateBpm)
            If CLng(tr.HeartRateBpm) > lngMaxHeartRateBpm Then
               lngMaxHeartRateBpm = CLng(tr.HeartRateBpm)
            End If

         End If

      Next
      If k <> 0 Then
         lngAveHeartRateBpm = lngAveHeartRateBpm / k
      End If


      Dim lngWeightKg As Long = 0

      lngWeightKg = CLng(txtWeightLbs.Text) * 0.45359237

      If lngAveHeartRateBpm <> 0 Then

         If txtSex.Text.ToLower = "male" Then
            lngCalories = (-55.0969 + 0.6309 * lngAveHeartRateBpm + 0.1988 * lngWeightKg + 0.2017 * CInt(txtAge.Text)) / 4.184
         Else
            lngCalories = (-20.4022 + 0.4472 * lngAveHeartRateBpm + 0.1263 * lngWeightKg + 0.074 * CInt(txtAge.Text)) / 4.184
         End If

         lngCalories = lngCalories * (lngTotalTimeSeconds / 60)
         WriteToFile("<Calories>" & lngCalories & "</Calories>")
         WriteToFile("<AverageHeartRateBpm><Value>" & lngAveHeartRateBpm & "</Value></AverageHeartRateBpm>")
         WriteToFile("<MaximumHeartRateBpm><Value>" & lngMaxHeartRateBpm & "</Value></MaximumHeartRateBpm>")
         WriteToFile("<Intensity>Active</Intensity>")
      Else
         'If no HR data from file use data from form
         If chkUseAvgHR.Checked = True Then
            lngAveHeartRateBpm = CInt(txtAvgBPMHR.Text)
            lngMaxHeartRateBpm = CInt(txtMaxBPMHR.Text)
            If txtSex.Text.ToLower = "male" Then
               lngCalories = (-55.0969 + 0.6309 * lngAveHeartRateBpm + 0.1988 * lngWeightKg + 0.2017 * CInt(txtAge.Text)) / 4.184
            Else
               lngCalories = (-20.4022 + 0.4472 * lngAveHeartRateBpm + 0.1263 * lngWeightKg + 0.074 * CInt(txtAge.Text)) / 4.184
            End If

            lngCalories = lngCalories * (lngTotalTimeSeconds / 60)
            WriteToFile("<Calories>" & lngCalories & "</Calories>")
            WriteToFile("<AverageHeartRateBpm><Value>" & lngAveHeartRateBpm & "</Value></AverageHeartRateBpm>")
            WriteToFile("<MaximumHeartRateBpm><Value>" & lngMaxHeartRateBpm & "</Value></MaximumHeartRateBpm>")
            WriteToFile("<Intensity>Active</Intensity>")
         Else

            WriteToFile("<Calories>" & 0 & "</Calories>")
            WriteToFile("<Intensity>Active</Intensity>")
         End If

      End If

      Dim j As Int16 = 0
      Dim lngAvgCadence As Long = 0
      For Each tr As Trackpoint In trackData.Trackpoints

         If Not IsNothing(tr.Cadence) Then
            j += 1
            lngAvgCadence += CLng(tr.Cadence)
         End If

      Next
      If j <> 0 Then
         lngAvgCadence = lngAvgCadence / j
      End If

      WriteToFile("<Cadence>" & lngAvgCadence & "</Cadence>")

      WriteToFile("<TriggerMethod>" & txtUnitLapTrigger.Text & "</TriggerMethod>")

      Dim strFileContent As String = System.IO.File.ReadAllText(strLapFileName)

      WriteToFile(strFileContent)

      'The closing tag

      WriteToFile("</Lap>")

   End Sub

   Private Sub btnExit_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnExit.Click
      btnSaveUserProfile.PerformClick()
      btnSaveUnitProfile.PerformClick()
      End
   End Sub

   Private Sub FixTrackXMLTag(ByVal strFileName As String)

      Dim from_string As String = "<Track>"
      Dim to_string As String = ""

      Dim file_info As New System.IO.FileInfo(strFileName)
      Try

         If (from_string Is Nothing) Then
         Else
            Dim strFileContent As String = System.IO.File.ReadAllText(file_info.FullName)
            If (strFileContent.Contains(from_string)) Then

               strFileContent = strFileContent.Replace("<Track>", "")
               strFileContent = strFileContent.Replace("</Track>", "")

               strFileContent = "<Track>" & strFileContent & "</Track>"

               If (to_string IsNot Nothing) Then
                  System.IO.File.WriteAllText(file_info.FullName, strFileContent)
               End If
            End If
         End If
      Catch ex As Exception
         WriteToLog("Error processing file " & file_info.FullName & "\n" & ex.Message)
         MessageBox.Show("Check Log -> Error processing file " & file_info.FullName & "\n" & ex.Message)
      End Try
   End Sub

   Private Sub btnCheckTCXFile_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCheckTCXFile.Click

      Dim myDocument As New XmlDocument
      myDocument.Load(m_strCTXOutputFileName)
      myDocument.Schemas.Add("http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2", "http://www.garmin.com/xmlschemas/TrainingCenterDatabasev2.xsd")
      Dim eventHandler As ValidationEventHandler = New ValidationEventHandler(AddressOf ValidationEventHandler)
      myDocument.Validate(eventHandler)

   End Sub

   Private Sub ValidationEventHandler(ByVal sender As Object, ByVal e As ValidationEventArgs)
      Select Case e.Severity
         Case XmlSeverityType.Error
            Debug.WriteLine("Error: {0}", e.Message)
            WriteToLog("Error: " & e.Message.ToString)
         Case XmlSeverityType.Warning
            Debug.WriteLine("Warning {0}", e.Message)
            WriteToLog("Error: " & e.Message.ToString)
      End Select
   End Sub

   Private Sub btnSaveUserProfile_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSaveUserProfile.Click
      My.Settings.UserAge = txtAge.Text
      My.Settings.UserWeight = txtWeightLbs.Text
      My.Settings.UserSex = txtSex.Text
      My.Settings.UserAvgBPMHR = txtAvgBPMHR.Text
      My.Settings.UserMaxBPMHR = txtMaxBPMHR.Text
      My.Settings.UseHRInputForCalories = chkUseAvgHR.Checked
      MsgBox("User Profile Saved!")
   End Sub

   Private Sub btnSaveUnitProfile_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSaveUnitProfile.Click
      My.Settings.UnitModel = txtUnitModel.Text
      My.Settings.UnitID = txtUnitID.Text
      My.Settings.LapTrigger = txtUnitLapTrigger.Text
      MsgBox("Unit Profile Saved!")
   End Sub

   Private Sub AboutToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles AboutToolStripMenuItem.Click
      AboutBox.Show()
   End Sub
End Class

Public Class Track
   Private _Trackpoints As List(Of Trackpoint)
   Public ReadOnly Property Trackpoints() As List(Of Trackpoint)
      Get
         Return _Trackpoints
      End Get
   End Property

   Public Sub New(ByVal x As System.Xml.XmlNode)
      _Trackpoints = New List(Of Trackpoint)     
      ParseXML(x)
      '_strLapStartTime = 
   End Sub

   Private Sub ParseXML(ByVal x As System.Xml.XmlNode)
      Dim xTrackpoints As System.Xml.XmlNodeList = x.SelectNodes("//Track/Trackpoint")
      If Not xTrackpoints Is Nothing Then
         For Each xs As System.Xml.XmlNode In xTrackpoints
            If Not xs Is Nothing Then
               _Trackpoints.Add(New Trackpoint(xs))
            End If
         Next
      End If

   End Sub
End Class

Public Class Trackpoint
   Private _Time As String
   Public ReadOnly Property Time() As String
      Get
         Return _Time
      End Get
   End Property

   Private _LatitudeDegrees As String
   Public ReadOnly Property LatitudeDegrees() As String
      Get
         Return _LatitudeDegrees
      End Get
   End Property

   Private _LongitudeDegrees As String
   Public ReadOnly Property LongitudeDegrees() As String
      Get
         Return _LongitudeDegrees
      End Get
   End Property

   Private _AltitudeMeters As String
   Public ReadOnly Property AltitudeMeters() As String
      Get
         Return _AltitudeMeters
      End Get
   End Property

   Private _DistanceMeters As String
   Public ReadOnly Property DistanceMeters() As String
      Get
         Return _DistanceMeters
      End Get
   End Property

   Private _HeartRateBpm As String
   Public ReadOnly Property HeartRateBpm() As String
      Get
         Return _HeartRateBpm
      End Get
   End Property

   Private _Cadence As String
   Public ReadOnly Property Cadence() As String
      Get
         Return _Cadence
      End Get
   End Property

   Private _SensorState As String
   Public ReadOnly Property SensorState() As String
      Get
         Return _SensorState
      End Get
   End Property


   Public Sub New(ByVal x As System.Xml.XmlNode)
      ParseXML(x)
   End Sub


   Private Sub ParseXML(ByVal x As System.Xml.XmlNode)

      Dim xTime As System.Xml.XmlNode = x.SelectSingleNode("Time")
      If Not xTime Is Nothing Then
         _Time = x.SelectSingleNode("Time").InnerText.Trim
      End If

      Dim xLatitudeDegrees As System.Xml.XmlNode = x.SelectSingleNode("Position/LatitudeDegrees")
      If Not xLatitudeDegrees Is Nothing Then
         _LatitudeDegrees = x.SelectSingleNode("Position/LatitudeDegrees").InnerText.Trim
      End If

      Dim xLongitudeDegrees As System.Xml.XmlNode = x.SelectSingleNode("Position/LongitudeDegrees")
      If Not xLongitudeDegrees Is Nothing Then
         _LongitudeDegrees = x.SelectSingleNode("Position/LongitudeDegrees").InnerText.Trim
      End If

      Dim xAltitudeMeters As System.Xml.XmlNode = x.SelectSingleNode("AltitudeMeters")
      If Not xAltitudeMeters Is Nothing Then
         _AltitudeMeters = x.SelectSingleNode("AltitudeMeters").InnerText.Trim
      End If

      Dim xDistanceMeters As System.Xml.XmlNode = x.SelectSingleNode("DistanceMeters")
      If Not xDistanceMeters Is Nothing Then
         _DistanceMeters = x.SelectSingleNode("DistanceMeters").InnerText.Trim
      End If

      Dim xHeartRateBpm As System.Xml.XmlNode = x.SelectSingleNode("HeartRateBpm/Value")
      If Not xHeartRateBpm Is Nothing Then
         _HeartRateBpm = x.SelectSingleNode("HeartRateBpm/Value").InnerText.Trim
      End If

      Dim xCadence As System.Xml.XmlNode = x.SelectSingleNode("Cadence")
      If Not xCadence Is Nothing Then
         _Cadence = x.SelectSingleNode("Cadence").InnerText.Trim
      End If

      Dim xSensorState As System.Xml.XmlNode = x.SelectSingleNode("SensorState")
      If Not xSensorState Is Nothing Then
         _SensorState = x.SelectSingleNode("SensorState").InnerText.Trim
      End If

   End Sub
End Class
